Skip to content

feat(publish): check whether a minted ID has already been used - #669

Open
ashleycaselli wants to merge 1 commit into
masterfrom
646-check-minted-ids-already-used
Open

feat(publish): check whether a minted ID has already been used#669
ashleycaselli wants to merge 1 commit into
masterfrom
646-check-minted-ids-already-used

Conversation

@ashleycaselli

Copy link
Copy Markdown
Member

Closes #646

The problem

When a new ID is minted in a way that doesn't include the artifact code of the nanopublication — defining a new space, say — nothing makes it unique. Filling the same form with the same name twice yields the same IRI, and the second nanopublication silently extends the first one's resource instead of defining a new one. A nanopublication can't be edited afterwards, so the mistake is only undone by a corrected version.

This is not hypothetical: https://w3id.org/spaces/example/bar currently has two introducing nanopublications, the second published while this was being written.

The change

The publish form asks the query API (get-introducing-nanopub, on the indexed meta repo) whether any identifier it minted is already introduced, and refuses to publish — naming the one that is taken — before signing. The same check guards the preview button, since the preview page publishes the nanopublication it was given without coming back through the form.

Which identifiers those are is knowable only from how the value was built, so TemplateContext records them as it mints them, kept apart from the introduced IRIs.

What is deliberately not checked

A blanket check over introduced IRIs breaks working flows, so four cases are exempt:

Case Why
IRI typed out in full Naming an existing thing, not minting one — "Defining an open-ended Space with existing URI" exists precisely for this
IRI under the nanopub's own namespace Picks up the artifact code at signing time; unique by construction
Auto-escaping placeholders AIDA sentences derive the IRI from the text: two people writing the same sentence are meant to arrive at the same IRI, so an existing one is agreement, not a collision
Supersede / override Keeping the source's identifier is the point (docs/fill-modes.md)

The carve-outs are drawn from the published templates rather than guessed. A survey of every template combining nt:IntroducedResource with nt:hasPrefix returns 30 prefix/placeholder-type pairs: all are genuine minting cases (spaces, I-ADOPT variables, Nanodash projects, 3PFF, ~~SPACE~~/) except the 17 AIDA templates, which are all AutoEscapeUriPlaceholder. The one https://orcid.org/ hit is the superseded "Introducing a user" template; the current one — the one Nanodash links to — uses a prefix-less agent placeholder, so re-introducing yourself with a new key is unaffected.

A query service that cannot be reached answers "not taken" and logs: a check that cannot be made is not evidence of a collision, and publishing shouldn't depend on the query services being up.

Testing

./mvnw -o test1243 tests, 0 failures.

  • New PrefixMintedIdTest (8 tests): one per row of the table above, plus the guard itself (taken / free / supersede-asks-nothing).
  • 3 tests added to QueryApiAccessTest for the lookup, including the fail-open path.
  • Verified against the live API that get-introducing-nanopub separates a taken space IRI (2 rows) from a free one (0 rows).

🤖 Generated with Claude Code

https://claude.ai/code/session_01K55p8T6uQoSv48C5SXueve

An identifier minted under a fixed prefix -- a new space's IRI, say --
carries no artifact code, so nothing makes it unique: the same form
filled with the same name twice yields the same IRI, and the second
nanopublication silently extends the first one's resource instead of
defining a new one. A nanopublication cannot be edited afterwards, so
the collision is worth catching before publishing rather than after.

The publish form now asks the query API whether any identifier it minted
is already introduced, and refuses to publish (or to build a preview,
which the preview page publishes without coming back through the form)
naming the one that is taken.

Which identifiers those are is knowable only from how the value was
built, so TemplateContext records them as it mints them. Four kinds are
deliberately left alone:

- an IRI typed out in full, which names an existing thing rather than
  minting one -- "Defining an open-ended Space with existing URI" exists
  precisely to do that;
- one minted under the new nanopublication's own namespace, whose
  artifact code is substituted at signing time;
- one built by an auto-escaping placeholder, where the IRI is derived
  from the text itself: two people writing the same AIDA sentence are
  meant to arrive at the same IRI, so an existing one is agreement, not
  a collision;
- supersede and override, where keeping the source's identifier is the
  point (docs/fill-modes.md).

The carve-outs are drawn from the published templates rather than
guessed: of the templates combining nt:IntroducedResource with
nt:hasPrefix, every one mints a new resource except the AIDA family,
which is exactly the auto-escaping kind. A query service that cannot be
reached answers "not taken": a check that cannot be made is not evidence
of a collision, and publishing should not depend on the query services
being up.

Closes #646

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K55p8T6uQoSv48C5SXueve
@ashleycaselli
ashleycaselli requested a review from tkuhn August 31, 2026 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Checking whether IDs to be minted already exist

1 participant